﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
using System.Diagnostics.Contracts;

namespace UnitTestUtils.UI
{
    /// <summary>
    /// Provides functions to trace WPF control structures.
    /// </summary>
    public class WPFTreeUtil
    {
        #region make CTOR private
        private WPFTreeUtil() { }
        #endregion

        /// <summary>
        /// Dumps the LogicalTree of WPF elements.
        /// </summary>
        /// <param name="depth"></param>
        /// <param name="obj">root WPF element</param>
        public static void PrintLogicalTree(int depth, object obj)
        {
            // Print the object with preceding spaces that represent its depth
            Debug.WriteLine("Logical Tree:");
            Debug.WriteLine("-------------\n\r");
            
            printLogicalTree(depth, obj);
        }

        private static void printLogicalTree(int depth, object obj)
        {
            // Print the object with preceding spaces that represent its depth
            Debug.WriteLine(new string(' ', depth) + obj);

            // Sometimes leaf nodes aren't DependencyObjects (e.g. strings)
            if (!(obj is DependencyObject))
                return;

            // Recursive call for each logical child
            foreach (object child in LogicalTreeHelper.GetChildren(obj as DependencyObject))
                printLogicalTree(depth + 1, child);
        }

        /// <summary>
        /// Dumps the VisualTree of WPF elements.
        /// </summary>
        /// <param name="depth"></param>
        /// <param name="obj">root WPF element</param>
        public static void PrintVisualTree(int depth, DependencyObject obj)
        {
            // Print the object with preceding spaces that represent its depth
            Debug.WriteLine("Visual Tree:");
            Debug.WriteLine("-------------\n\r");

            printVisualTree(depth, obj);
        }

        private static void printVisualTree(int depth, DependencyObject obj)
        {
            // Print the object with preceding spaces that represent its depth
            Debug.WriteLine(new string(' ', depth) + obj);

            // Recursive call for each visual child
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
                printVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i));
        }

        /// <summary>
        /// Finds the first occurrance of TItem within the logical tree.
        /// </summary>
        /// <typeparam name="TItem">type of visual tree element to find.</typeparam>
        /// <param name="obj">root item to start searching at</param>
        /// <returns>item that matches specification</returns>
        public static TItem FindItem<TItem>(DependencyObject obj)
            where TItem : FrameworkElement
        {
            Contract.Requires<ArgumentNullException>(obj != null, "obj");
            return FindItem<TItem>(obj, (string)null);
        }

        /// <summary>
        /// Finds the first occurrance of TItem within the logical tree.
        /// </summary>
        /// <typeparam name="TItem">type of visual tree element to find.</typeparam>
        /// <param name="obj">root item to start searching at</param>
        /// <param name="name">name of the item or null in case name does not need to match.</param>
        /// <returns>item that matches specification</returns>
        public static TItem FindItem<TItem>(DependencyObject obj, string name)
            where TItem : FrameworkElement
        {
            Contract.Requires<ArgumentNullException>(obj != null, "obj");
            IComparer<string> comparer = (!string.IsNullOrEmpty(name)) ? 
                (IComparer<string>)StringComparer.Ordinal : new NullComparer();

            TItem foundItem = null;

            foreach (var childItem in LogicalTreeHelper.GetChildren(obj))
            {
                TItem typedItem = childItem as TItem;
                if (typedItem != null && comparer.Compare(typedItem.Name, name) == 0)
                {
                    return (TItem)childItem;
                }
                
                if (childItem is DependencyObject)
                    foundItem = FindItem<TItem>((DependencyObject)childItem, name);
            }
            return foundItem;
        }

        /// <summary>
        /// Finds an item within the visual tree that matches the passed filter criteria.
        /// </summary>
        /// <typeparam name="TItem"></typeparam>
        /// <param name="obj"></param>
        /// <param name="filter"></param>
        /// <returns></returns>
        public static TItem FindItemVisual<TItem>(DependencyObject obj, Func<TItem, bool> filter)
            where TItem : FrameworkElement
        {
            Contract.Requires<ArgumentNullException>(obj != null, "obj");
            Contract.Requires<ArgumentNullException>(filter != null, "filter");

            return findItemVisual<TItem>(obj, filter);
        }

        private static TItem findItemVisual<TItem>(DependencyObject obj, Func<TItem, bool> filter)
            where TItem : FrameworkElement
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                var childItem = VisualTreeHelper.GetChild(obj, i);

                TItem typedItem = childItem as TItem;
                if (typedItem != null && filter(typedItem))
                    return typedItem;

                // traverse child items
                if (childItem is DependencyObject)
                {
                    typedItem = FindItemVisual<TItem>((DependencyObject)childItem, filter);
                    if (typedItem != null && filter(typedItem))
                        return typedItem;
                }
            }
            return null;
        }

        /// <summary>
        /// Finds the first occurrance of TItem within the logical tree.
        /// </summary>
        /// <typeparam name="TItem">type of visual tree element to find.</typeparam>
        /// <param name="obj">root item to start searching at</param>
        /// <param name="filter">filter for item selection</param>
        /// <returns>item that matches specification</returns>
        public static TItem FindItem<TItem>(DependencyObject obj, Func<TItem,bool> filter)
            where TItem : FrameworkElement
        {
            Contract.Requires<ArgumentNullException>(obj != null, "obj");
            Contract.Requires<ArgumentNullException>(filter != null, "filter");

            TItem foundItem = null;

            foreach (var childItem in LogicalTreeHelper.GetChildren(obj))
            {
                TItem typedItem = childItem as TItem;
                if (typedItem != null && filter(typedItem))
                {
                    return (TItem)childItem;
                }

                if (childItem is DependencyObject)
                    foundItem = FindItem<TItem>((DependencyObject)childItem, filter);
            }
            return foundItem;
        }

        /// <summary>
        /// Always returns that strings are matching.
        /// </summary>
        private class NullComparer : IComparer<string>
        {
            #region IComparer<string> Members
            public int Compare(string x, string y)
            {
                return 0;
            }
            #endregion
        }

    }
}
